/*
 * Copyright 2010 Nicholas Alexander Cross
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 	http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.intellij.lang.plist.parser;

import com.intellij.lang.PsiBuilder;
import com.intellij.openapi.util.Key;
import com.intellij.psi.tree.IElementType;

public class DictionaryParser {
    final Key<Integer> dictionaryBraceCounter = new Key<Integer>("dictionary_brace");
    final Key<Boolean> rootOpened = new Key<Boolean>("root_opened");

    private ArrayParser arrayParser;

    public DictionaryParser() {
    }

    public void setArrayParser(ArrayParser parser) {
        this.arrayParser = parser;
    }

    void checkForInvalidTokens(PsiBuilder builder, IElementType tokenType) {

        if (tokenType == PListElementTypes.RIGHT_BRACE) {
            int i = dictionaryBraceCount(builder);

            if (i < 0) {
                builder.error("unexpected dictionary closure");
            } else if (!isRootOpened(builder)) {
                builder.error("dictionary closed before it was opened");
            }

            //make not of invalid tokens
            setDictionaryBraceCounter(builder, i - 1);
        }

    }

    boolean isValueToken(IElementType valueToken) {
        return valueToken == PListElementTypes.STRING || valueToken == PListElementTypes.NUMBER || valueToken == PListElementTypes.LEFT_BRACE || valueToken == PListElementTypes.DICTIONARY_KEY || valueToken == PListElementTypes.LEFT_PARENTHESES;
    }

    boolean isKeyToken(IElementType valueToken) {
        return valueToken == PListElementTypes.STRING || valueToken == PListElementTypes.DICTIONARY_KEY;
    }

    void parseDictionary(PsiBuilder builder) {

        setDictionaryBraceCounter(builder, dictionaryBraceCount(builder) + 1);

        PsiBuilder.Marker dictionary = builder.mark();

        builder.advanceLexer();

        IElementType keyToken = builder.getTokenType();

        if (isKeyToken(keyToken)) {
            parseDictionaryKeyValue(keyToken, builder, dictionary);
        } else if (keyToken == PListElementTypes.RIGHT_BRACE) {
            closeDictionary(builder, dictionary);
        } else {
            builder.error("Invalid dictionary");
            dictionary.drop();
        }
    }

    void parseDictionaryKeyValue(IElementType valueToken, PsiBuilder builder, PsiBuilder.Marker dictionary) {
        if (isKeyToken(valueToken)) {
            PsiBuilder.Marker dictionaryValue = builder.mark();
            builder.advanceLexer();

            final IElementType equalsToken = builder.getTokenType();

            if (PListElementTypes.EQUALS == equalsToken) {
                builder.advanceLexer();
                parseDictionaryValue(builder.getTokenType(), builder, dictionary, dictionaryValue);
            }
            else {
                builder.error("= expected");
                dictionaryValue.drop();
                dictionary.drop();
            }
        } else {
            builder.error("Dictionary key expected");
            dictionary.drop();
        }

    }

    private void parseDictionaryValue(IElementType valueToken, PsiBuilder builder, PsiBuilder.Marker dictionary, PsiBuilder.Marker dictionaryValue) {

        if (isValueToken(valueToken)) {

            if (valueToken == PListElementTypes.STRING || valueToken == PListElementTypes.NUMBER || valueToken == PListElementTypes.DICTIONARY_KEY) {
                builder.advanceLexer();
            } else if (valueToken == PListElementTypes.LEFT_BRACE) {
                parseDictionary(builder);
            } else if (valueToken == PListElementTypes.LEFT_PARENTHESES) {
                arrayParser.parseArray( builder );
            }
            else {
                builder.error("Dictionary value expected!");
            }

            IElementType separatorToken = builder.getTokenType();

            if (separatorToken == PListElementTypes.SEMI_COLON) {
                builder.advanceLexer();
                IElementType nextValueToken = builder.getTokenType();

                dictionaryValue.done(PListElementTypes.DICTIONARY_VALUE);

                if (nextValueToken == PListElementTypes.RIGHT_BRACE) {
                    closeDictionary(builder, dictionary);
                }
                else if (isValueToken(nextValueToken)) {
                    parseDictionaryKeyValue(nextValueToken, builder, dictionary);
                }
                else {
                    builder.error("dictionary key or close dictionary expected!");
                    dictionary.drop();
                }

            }
            else {
                builder.error("; expected");
                dictionaryValue.drop();
                dictionary.drop();
            }
        } else {
            builder.error("dictionary value expected!");
            dictionaryValue.drop();
            dictionary.drop();
        }
    }

    void closeDictionary(PsiBuilder builder, PsiBuilder.Marker array) {
        final int braceCount = dictionaryBraceCount(builder) - 1;

        setDictionaryBraceCounter(builder, braceCount);

        builder.advanceLexer();

        array.done(PListElementTypes.DICTIONARY);
    }

    void setDictionaryBraceCounter(PsiBuilder builder, int value) {
        builder.putUserData(
                dictionaryBraceCounter,
                value
        );
    }

    int dictionaryBraceCount(PsiBuilder builder) {
        final Integer data = builder.getUserData(dictionaryBraceCounter);
        return data != null ? data : 0;
    }

    void setRootOpened(PsiBuilder builder, boolean value) {
        builder.putUserData(
                rootOpened,
                value
        );
    }

    boolean isRootOpened(PsiBuilder builder) {
        final Boolean data = builder.getUserData(rootOpened);
        return data != null ? data : false;
    }
}